home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / Console / Table.php < prev   
Encoding:
PHP Script  |  2006-04-07  |  16.7 KB  |  562 lines

  1. <?php
  2. // +-----------------------------------------------------------------------+
  3. // | Copyright (c) 2002-2003 Richard Heyes                                 |
  4. // | All rights reserved.                                                  |
  5. // |                                                                       |
  6. // | Redistribution and use in source and binary forms, with or without    |
  7. // | modification, are permitted provided that the following conditions    |
  8. // | are met:                                                              |
  9. // |                                                                       |
  10. // | o Redistributions of source code must retain the above copyright      |
  11. // |   notice, this list of conditions and the following disclaimer.       |
  12. // | o Redistributions in binary form must reproduce the above copyright   |
  13. // |   notice, this list of conditions and the following disclaimer in the |
  14. // |   documentation and/or other materials provided with the distribution.|
  15. // | o The names of the authors may not be used to endorse or promote      |
  16. // |   products derived from this software without specific prior written  |
  17. // |   permission.                                                         |
  18. // |                                                                       |
  19. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
  20. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
  21. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  22. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
  23. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  24. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
  25. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  26. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  27. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
  28. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  29. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
  30. // |                                                                       |
  31. // +-----------------------------------------------------------------------+
  32. // | Author: Richard Heyes <richard@phpguru.org>                           |
  33. // |         Jan Schneider <jan@horde.org>                                 |
  34. // +-----------------------------------------------------------------------+
  35. //
  36. // $Id: Table.php,v 1.15 2006/03/12 23:34:18 yunosh Exp $
  37. //
  38. // Utility for printing tables from cmdline scripts
  39. //
  40.  
  41. define('CONSOLE_TABLE_HORIZONTAL_RULE', 1);
  42. define('CONSOLE_TABLE_ALIGN_LEFT', -1);
  43. define('CONSOLE_TABLE_ALIGN_CENTER', 0);
  44. define('CONSOLE_TABLE_ALIGN_RIGHT', 1);
  45.  
  46. class Console_Table
  47. {
  48.     /**
  49.      * The table headers.
  50.      *
  51.      * @var array
  52.      */
  53.     var $_headers = array();
  54.  
  55.     /**
  56.      * The data of the table.
  57.      *
  58.      * @var array
  59.      */
  60.     var $_data = array();
  61.  
  62.     /**
  63.      * The max number of columns in a row.
  64.      *
  65.      * @var integer
  66.      */
  67.     var $_max_cols = 0;
  68.  
  69.     /**
  70.      * The max number of rows in the table.
  71.      *
  72.      * @var integer
  73.      */
  74.     var $_max_rows = 0;
  75.  
  76.     /**
  77.      * Lengths of the columns, calculated when rows are added to the table.
  78.      *
  79.      * @var array
  80.      */
  81.     var $_cell_lengths = array();
  82.  
  83.     /**
  84.      * How many spaces to use to pad the table.
  85.      *
  86.      * @var integer
  87.      */
  88.     var $_padding = 1;
  89.  
  90.     /**
  91.      * Column filters.
  92.      *
  93.      * @var array
  94.      */
  95.     var $_filters = array();
  96.  
  97.     /**
  98.      * Columns to calculate totals for.
  99.      *
  100.      * @var array
  101.      */
  102.     var $_calculateTotals;
  103.  
  104.     /**
  105.      * Alignment of the columns.
  106.      *
  107.      * @var array
  108.      */
  109.     var $_col_align = array();
  110.  
  111.     /**
  112.      * Default alignment of columns.
  113.      *
  114.      * @var integer
  115.      */
  116.     var $_defaultAlign;
  117.  
  118.     /**
  119.      * Charset of the data.
  120.      *
  121.      * @var string
  122.      */
  123.     var $_charset = 'utf-8';
  124.  
  125.     /**
  126.      * Constructor.
  127.      *
  128.      * @param integer $align  Default alignment
  129.      */
  130.     function Console_Table($align = CONSOLE_TABLE_ALIGN_LEFT)
  131.     {
  132.         $this->_defaultAlign = $align;
  133.     }
  134.  
  135.     /**
  136.      * Converts an array to a table. Must be 
  137.      *
  138.      * @static
  139.      *
  140.      * @param array $headers         Headers for the table.
  141.      * @param array $data            A two dimensional array with the table
  142.      *                               data.
  143.      * @param boolean $returnObject  Whether to return the Console_Table object
  144.      *                               instead of the rendered table.
  145.      *
  146.      * @return Console_Table|string  A Console_Table object or the generated
  147.      *                               table.
  148.      */
  149.     function fromArray($headers, $data, $returnObject = false)
  150.     {
  151.         if (!is_array($headers) || !is_array($data)) {
  152.             return false;
  153.         }
  154.  
  155.         $table = &new Console_Table();
  156.         $table->setHeaders($headers);
  157.  
  158.         foreach ($data as $row) {
  159.             $table->addRow($row);
  160.         }
  161.  
  162.         return $returnObject ? $table : $table->getTable();
  163.     }
  164.  
  165.     /**
  166.      * Adds a filter to a column.
  167.      *
  168.      * Filters are standard PHP callbacks which are run on the data before
  169.      * table generation is performed. Filters are applied in the order they
  170.      * are added. The callback function must accept a single argument, which
  171.      * is a single table cell.
  172.      *
  173.      * @param integer $col     Column to apply filter to.
  174.      * @param mixed $callback  PHP callback to apply.
  175.      */
  176.     function addFilter($col, &$callback)
  177.     {
  178.         $this->_filters[] = array($col, &$callback);
  179.     }
  180.  
  181.     /**
  182.      * Sets the charset of the provided table data.
  183.      *
  184.      * @string $charset  A charset supported by the mbstring PHP extension.
  185.      */
  186.     function setCharset($charset)
  187.     {
  188.         $this->_charset = strtolower($charset);
  189.     }
  190.  
  191.     /**
  192.      * Sets the alignment for the columns.
  193.      *
  194.      * @param integer $col_id  The column number.
  195.      * @param integer $align   Alignment to set for this column. One of
  196.      *                         CONSOLE_TABLE_ALIGN_LEFT
  197.      *                         CONSOLE_TABLE_ALIGN_CENTER
  198.      *                         CONSOLE_TABLE_ALIGN_RIGHT.
  199.      */
  200.     function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT)
  201.     {
  202.         switch ($align) {
  203.             case CONSOLE_TABLE_ALIGN_CENTER:
  204.                 $pad = STR_PAD_BOTH;
  205.                 break;
  206.             case CONSOLE_TABLE_ALIGN_RIGHT:
  207.                 $pad = STR_PAD_LEFT;
  208.                 break;
  209.             default:
  210.                 $pad = STR_PAD_RIGHT;
  211.                 break;
  212.         }
  213.         $this->_col_align[$col_id] = $pad;
  214.     }
  215.  
  216.     /**
  217.      * Specifies which columns are to have totals calculated for them and
  218.      * added as a new row at the bottom.
  219.      *
  220.      * @param array $cols  Array of column numbers (starting with 0).
  221.      */
  222.     function calculateTotalsFor($cols)
  223.     {
  224.         $this->_calculateTotals = $cols;
  225.     }
  226.  
  227.     /**
  228.      * Sets the headers for the columns.
  229.      *
  230.      * @param array $headers  The column headers.
  231.      */
  232.     function setHeaders($headers)
  233.     {
  234.         $this->_headers = array_values($headers);
  235.         $this->_updateRowsCols($headers);
  236.     }
  237.  
  238.     /**
  239.      * Adds a row to the table.
  240.      *
  241.      * @param array $row       The row data to add.
  242.      * @param boolean $append  Whether to append or prepend the row.
  243.     */
  244.     function addRow($row, $append = true)
  245.     {
  246.         if ($append) {
  247.             $this->_data[] = array_values($row);
  248.         } else {
  249.             array_unshift($this->_data, array_values($row));
  250.         }
  251.  
  252.         $this->_updateRowsCols($row);
  253.     }
  254.  
  255.     /**
  256.      * Inserts a row after a given row number in the table.
  257.      *
  258.      * If $row_id is not given it will prepend the row.
  259.      *
  260.      * @param array $row       The data to insert.
  261.      * @param integer $row_id  Row number to insert before.
  262.      */
  263.     function insertRow($row, $row_id = 0)
  264.     {
  265.         array_splice($this->_data, $row_id, 0, array($row));
  266.  
  267.         $this->_updateRowsCols($row);
  268.     }
  269.  
  270.     /**
  271.      * Adds a column to the table.
  272.      *
  273.      * @param array $col_data  The data of the column.
  274.      * @param integer $col_id  The column index to populate.
  275.      * @param integer $row_id  If starting row is not zero, specify it here.
  276.      */
  277.     function addCol($col_data, $col_id = 0, $row_id = 0)
  278.     {
  279.         foreach ($col_data as $col_cell) {
  280.             $this->_data[$row_id++][$col_id] = $col_cell;
  281.         }
  282.  
  283.         $this->_updateRowsCols();
  284.         $this->_max_cols = max($this->_max_cols, $col_id + 1);
  285.     }
  286.  
  287.     /**
  288.      * Adds data to the table.
  289.      *
  290.      * @param array $data      A two dimensional array with the table data.
  291.      * @param integer $col_id  Starting column number.
  292.      * @param integer $row_id  Starting row number.
  293.      */
  294.     function addData($data, $col_id = 0, $row_id = 0)
  295.     {
  296.         foreach ($data as $row) {
  297.             $starting_col = $col_id;
  298.             foreach ($row as $cell) {
  299.                 $this->_data[$row_id][$starting_col++] = $cell;
  300.             }
  301.             $this->_updateRowsCols();
  302.             $this->_max_cols = max($this->_max_cols, $starting_col);
  303.             $row_id++;
  304.         }
  305.     }
  306.  
  307.     /**
  308.      * Adds a horizontal seperator to the table.
  309.      */
  310.     function addSeparator()
  311.     {
  312.         $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE;
  313.     }
  314.  
  315.     /*
  316.      * Returns the table in wonderful ASCII art.
  317.      *
  318.      * @return string  The generated table.
  319.      */
  320.     function getTable()
  321.     {
  322.         $this->_applyFilters();
  323.         $this->_calculateTotals();
  324.         $this->_validateTable();
  325.  
  326.         return $this->_buildTable();
  327.     }
  328.  
  329.     /**
  330.      * Calculates totals for columns.
  331.      */
  332.     function _calculateTotals()
  333.     {
  334.         if (!empty($this->_calculateTotals)) {
  335.             $this->addSeparator();
  336.  
  337.             $totals = array();
  338.             foreach ($this->_data as $row) {
  339.                 if (is_array($row)) {
  340.                     foreach ($this->_calculateTotals as $columnID) {
  341.                         $totals[$columnID] += $row[$columnID];
  342.                     }
  343.                 }
  344.             }
  345.  
  346.             $this->_data[] = $totals;
  347.             $this->_updateRowsCols();
  348.         }
  349.     }
  350.  
  351.     /**
  352.      * Applies any column filters to the data.
  353.      */
  354.     function _applyFilters()
  355.     {
  356.         if (!empty($this->_filters)) {
  357.             foreach ($this->_filters as $filter) {
  358.                 $column   = $filter[0];
  359.                 $callback = $filter[1];
  360.  
  361.                 foreach ($this->_data as $row_id => $row_data) {
  362.                     $this->_data[$row_id][$column] = call_user_func($callback, $row_data[$column]);
  363.                 }
  364.             }
  365.         }
  366.     }
  367.  
  368.     /**
  369.      * Ensures column and row counts are correct.
  370.      */
  371.     function _validateTable()
  372.     {
  373.         for ($i = 0; $i < $this->_max_rows; $i++) {
  374.             for ($j = 0; $j < $this->_max_cols; $j++) {
  375.                 if (!isset($this->_data[$i][$j]) &&
  376.                     (!isset($this->_data[$i]) ||
  377.                      $this->_data[$i] != CONSOLE_TABLE_HORIZONTAL_RULE)) {
  378.                     $this->_data[$i][$j] = '';
  379.                 }
  380.  
  381.                 // Update cell lengths
  382.                 $this->_calculateCellLengths($this->_data[$i]);
  383.             }
  384.  
  385.             if ($this->_data[$i] != CONSOLE_TABLE_HORIZONTAL_RULE) {
  386.                  ksort($this->_data[$i]);
  387.             }
  388.  
  389.         }
  390.  
  391.         ksort($this->_data);
  392.     }
  393.  
  394.     /**
  395.      * Builds the table.
  396.      */
  397.     function _buildTable()
  398.     {
  399.         $return = array();
  400.         $rows   = $this->_data;
  401.  
  402.         for ($i = 0; $i < count($rows); $i++) {
  403.             for ($j = 0; $j < count($rows[$i]); $j++) {
  404.                 if ($rows[$i] != CONSOLE_TABLE_HORIZONTAL_RULE &&
  405.                     $this->_strlen($rows[$i][$j]) < $this->_cell_lengths[$j]) {
  406.                     $rows[$i][$j] = str_pad($rows[$i][$j],
  407.                                             $this->_cell_lengths[$j],
  408.                                             ' ',
  409.                                             $this->_col_align[$j]);
  410.                 }
  411.             }
  412.  
  413.             if ($rows[$i] != CONSOLE_TABLE_HORIZONTAL_RULE) {
  414.                 $row_begin    = '|' . str_repeat(' ', $this->_padding);
  415.                 $row_end      = str_repeat(' ', $this->_padding) . '|';
  416.                 $implode_char = str_repeat(' ', $this->_padding) . '|' .
  417.                     str_repeat(' ', $this->_padding);
  418.                 $return[] = $row_begin . implode($implode_char, $rows[$i]) .
  419.                     $row_end;
  420.             } else {
  421.                 $return[] = $this->_getSeparator();
  422.             }
  423.  
  424.         }
  425.  
  426.         $return = $this->_getSeparator() . "\r\n" . implode("\n", $return) .
  427.             "\r\n" . $this->_getSeparator() . "\r\n";
  428.  
  429.         if (!empty($this->_headers)) {
  430.             $return = $this->_getHeaderLine() .  "\r\n" . $return;
  431.         }
  432.  
  433.         return $return;
  434.     }
  435.  
  436.     /**
  437.      * Creates a horizontal separator for header separation and table
  438.      * start/end etc.
  439.      */
  440.     function _getSeparator()
  441.     {
  442.         foreach ($this->_cell_lengths as $cl) {
  443.             $return[] = str_repeat('-', $cl);
  444.         }
  445.  
  446.         $row_begin    = '+' . str_repeat('-', $this->_padding);
  447.         $row_end      = str_repeat('-', $this->_padding) . '+';
  448.         $implode_char = str_repeat('-', $this->_padding) . '+' .
  449.             str_repeat('-', $this->_padding);
  450.  
  451.         return $row_begin . implode($implode_char, $return) . $row_end;
  452.     }
  453.  
  454.     /**
  455.      * Returns header line for the table.
  456.      */
  457.     function _getHeaderLine()
  458.     {
  459.         // Make sure column count is correct
  460.         for ($i = 0;  $i < $this->_max_cols; $i++) {
  461.             if (!isset($this->_headers[$i])) {
  462.                 $this->_headers[$i] = '';
  463.             }
  464.         }
  465.  
  466.         for ($i = 0; $i < count($this->_headers); $i++) {
  467.             if ($this->_strlen($this->_headers[$i]) < $this->_cell_lengths[$i]) {
  468.                 $this->_headers[$i] = str_pad($this->_headers[$i],
  469.                                               $this->_cell_lengths[$i],
  470.                                               ' ',
  471.                                               $this->_col_align[$i]);
  472.             }
  473.         }
  474.  
  475.         $row_begin    = '|' . str_repeat(' ', $this->_padding);
  476.         $row_end      = str_repeat(' ', $this->_padding) . '|';
  477.         $implode_char = str_repeat(' ', $this->_padding) . '|' .
  478.             str_repeat(' ', $this->_padding);
  479.  
  480.         $return[] = $this->_getSeparator();
  481.         $return[] = $row_begin . implode($implode_char, $this->_headers) .
  482.             $row_end;
  483.  
  484.         return implode("\r\n", $return);
  485.     }
  486.  
  487.     /**
  488.      * Update max cols/rows.
  489.      */
  490.     function _updateRowsCols($rowdata = null)
  491.     {
  492.         // Update max cols
  493.         $this->_max_cols = max($this->_max_cols, count($rowdata));
  494.  
  495.         // Update max rows
  496.         ksort($this->_data);
  497.         $keys = array_keys($this->_data);
  498.         $this->_max_rows = end($keys) + 1;
  499.  
  500.         switch ($this->_defaultAlign) {
  501.             case CONSOLE_TABLE_ALIGN_CENTER: $pad = STR_PAD_BOTH; break;
  502.             case CONSOLE_TABLE_ALIGN_RIGHT:  $pad = STR_PAD_LEFT; break;
  503.             default:                         $pad = STR_PAD_RIGHT; break;
  504.         }
  505.  
  506.         // Set default column alignments
  507.         for ($i = count($this->_col_align); $i < $this->_max_cols; $i++) {
  508.             $this->_col_align[$i] = $pad;
  509.         }
  510.     }
  511.  
  512.     /**
  513.      * This function given a row of data will calculate the max length for
  514.      * each column and store it in the _cell_lengths array.
  515.      *
  516.      * @param array $row  The row data.
  517.      */
  518.     function _calculateCellLengths($row)
  519.     {
  520.         for ($i = 0; $i < count($row); $i++) {
  521.             if (!isset($this->_cell_lengths[$i])) {
  522.                 $this->_cell_lengths[$i] = 0;
  523.             }
  524.             $this->_cell_lengths[$i] = max(isset($this->_headers[$i]) ? $this->_strlen($this->_headers[$i]) : 0,
  525.                                            $this->_cell_lengths[$i],
  526.                                            $this->_strlen($row[$i]));
  527.         }
  528.     }
  529.  
  530.     /**
  531.      * Returns the character length of a string.
  532.      *
  533.      * @param string $str  A multibyte or singlebyte string.
  534.      *
  535.      * @return integer  The string length.
  536.      */
  537.     function _strlen($str)
  538.     {
  539.         static $mbstring, $utf8;
  540.  
  541.         // Cache expensive function_exists() calls.
  542.         if (!isset($mbstring)) {
  543.             $mbstring = function_exists('mb_strlen');
  544.         }
  545.         if (!isset($utf8)) {
  546.             $utf8 = function_exists('utf8_decode');
  547.         }
  548.  
  549.         if ($utf8 &&
  550.             ($this->_charset == strtolower('utf-8') ||
  551.              $this->_charset == strtolower('utf8'))) {
  552.             return strlen(utf8_decode($str));
  553.         }
  554.         if ($mbstring) {
  555.             return mb_strlen($str, $this->_charset);
  556.         }
  557.  
  558.         return strlen($str);
  559.     }
  560.  
  561. }
  562.